import java.lang.reflect.Field;
import java.net.*;
import java.io.*;

/**
 * 模拟Modbus RTU Over TCP的情况，模拟Poll端（Master端）
 * 大端模式
 */
public class Main {

    public static void main(String[] argv) {
        Socket socket = null;
        try {
            System.out.println("connecting...");
            socket = new Socket("127.0.0.1", 502);
            System.out.println("connection success");

//            String str = "01 03 00 00 00 22"; //发送的16进制字符串
            String slaveID = "01"; //从机编号
            String funcCode = "03"; //功能码
            String address = "00 00"; //起始地址
            String quantity = "00 22"; //寄存器数量
            byte[] bytes = hexStringToByteArrayWithCRC16(slaveID + funcCode + address + quantity); // 按照Modbus RTU拼接报文
            System.out.println("Send: " + bytesToHexString(bytes));
            OutputStream os = socket.getOutputStream();
            BufferedOutputStream bos = new BufferedOutputStream(os);
            bos.write(bytes);
            bos.flush();

            //读取服务器返回的消息
            byte[] receivedBytes = new byte[3];
            socket.getInputStream().read(receivedBytes);
            System.out.println("Receive Head: " + bytesToHexString(receivedBytes));
            if (receivedBytes[0] == bytes[0] && receivedBytes[1] == bytes[1]) {
                int len = Byte.toUnsignedInt(receivedBytes[2]);
                byte[] receivedData = new byte[len + 2];
                socket.getInputStream().read(receivedData);
                System.out.println("Receive Data: " +bytesToHexString(receivedData));

                // 按点表处理数据
                int offset = 0;
                Data data = new Data();
                for (PointInfo p : PointTable.pointTable) {
                    setValueToEntityByPointData(receivedData, offset, data, p);
                    offset = offset + p.getLen();
                }
                System.out.println(data);
                // 此处得到的data对象就是读出来的数据实体了
            } else {
                System.out.println("ERR");
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (Exception e) {

                }
            }
        }
    }

    /**
     * 16进制表示的字符串转换为字节数组，并附加两位CRC16校验
     *
     * @param hexString 16进制表示的字符串
     * @return byte[] 字节数组
     */
    public static byte[] hexStringToByteArrayWithCRC16(String hexString) {
        byte[] bytes = hexStringToByteArray(hexString);
        byte[] bytesWithCRC16 = new byte[bytes.length + 2];
        byte[] crc16 = hexStringToByteArray(getCRC(bytes));
        for (int i = 0; i < bytes.length; i++) {
            bytesWithCRC16[i] = bytes[i];
        }
        bytesWithCRC16[bytes.length] = crc16[0];
        bytesWithCRC16[bytes.length + 1] = crc16[1];
        return bytesWithCRC16;
    }

    /**
     * 16进制表示的字符串转换为字节数组
     *
     * @param hexString 16进制表示的字符串
     * @return byte[] 字节数组
     */
    public static byte[] hexStringToByteArray(String hexString) {
        hexString = hexString.replaceAll(" ", "");
        int len = hexString.length();
        byte[] bytes = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            // 两位一组，表示一个字节,把这样表示的16进制字符串，还原成一个字节
            bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character
                    .digit(hexString.charAt(i + 1), 16));
        }
        return bytes;
    }

    /**
     * byte[]数组转换为16进制的字符串
     *
     * @param bytes 要转换的字节数组
     * @return 转换后的结果
     */
    public static String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
            sb.append(" ");
        }
        return sb.toString();
    }

    /**
     * 计算CRC16校验码
     *
     * @param bytes
     * @return
     */
    public static String getCRC(byte[] bytes) {
        int CRC = 0x0000ffff;
        int POLYNOMIAL = 0x0000a001;

        int i, j;
        for (i = 0; i < bytes.length; i++) {
            CRC ^= ((int) bytes[i] & 0x000000ff);
            for (j = 0; j < 8; j++) {
                if ((CRC & 0x00000001) != 0) {
                    CRC >>= 1;
                    CRC ^= POLYNOMIAL;
                } else {
                    CRC >>= 1;
                }
            }
        }
        CRC = ((CRC & 0x0000FF00) >> 8) | ((CRC & 0x000000FF) << 8);
        return Integer.toHexString(CRC);
    }

    /**
     * 4字节byte数组转float
     *
     * @param b
     * @return
     */
    public static float bytesToFloat(byte... b) {
        int accum = 0;
        accum = accum | (b[3] & 0xff) << 0;
        accum = accum | (b[2] & 0xff) << 8;
        accum = accum | (b[1] & 0xff) << 16;
        accum = accum | (b[0] & 0xff) << 24;
        float f = Float.intBitsToFloat(accum);
        return f;
    }

    /**
     * 两位bytes转无符号short，
     * 【特殊说明】java的有short都是有符号的，所以结果需要用int来存，否则像65535就会出错
     *
     * @return 一个int值
     */
    public static int bytesToUnsignedShort(byte high, byte low) {
        return (int) ((high & 0xff) << 8) | (low & 0xff);
    }

    /**
     * 根据点表处理数据，并赋值到实体类中
     * @param rawBytes 原始二进制数据
     * @param offset 偏移量
     * @param entity 数据实体
     * @param p 点表
     */
    private static void setValueToEntityByPointData(byte[] rawBytes, int offset, Object entity, PointInfo p) {
        byte[] b = new byte[p.getLen()];
        System.arraycopy(rawBytes, offset, b, 0, p.getLen());
        switch (p.getType()) {
            case "u8": // 处理8位无符号数
            case "u16":
                // 处理16位无符号数
                Integer us = bytesToUnsignedShort(b[0], b[1]);
                setFieldValueByFieldName(p.getEntityFieldName(), entity, us.getClass(), us);
                break;
            case "skip":
                break;
            case "float":
                Float bigDecimal = bytesToFloat(b);
//                log.info("{}", bigDecimal);
                setFieldValueByFieldName(p.getEntityFieldName(), entity, bigDecimal.getClass(), bigDecimal);
                break;
        }
    }

    /**
     * 根据属性名设置属性值
     * https://blog.csdn.net/qiesheng/article/details/79702163
     *
     * @param fieldName 属性名
     * @param object 被赋值的对象实体
     * @param classType 此属性的类
     * @param value 此属性的对象
     * @return
     */
    private static <T> void setFieldValueByFieldName(String fieldName, Object object, Class<T> classType, Object value) {
        try {
            // 获取obj类的字节文件对象
            Class c = object.getClass();
            // 获取该类的成员变量
            Field f = c.getDeclaredField(fieldName);
            // 取消语言访问检查
            f.setAccessible(true);
            // 给变量赋值
            f.set(object, (T) value);
        } catch (Exception e) {
//            log.error(e.getMessage(), e);
        }
    }


}
